interface EffectOpt {
  lazy?: boolean;
  scheduler?: () => any;
}
interface EffectFn {
  (): any;
  scheduler: (() => any) | undefined;
}

const targetMap = new WeakMap<object, Map<symbol | string, Set<EffectFn>>>();

let activeEffect: EffectFn | null;

export function effect(fn: () => any, option: EffectOpt = {}) {
  const effectFn: EffectFn = () => {
    try {
      activeEffect = effectFn;
      return fn();
    } finally {
      activeEffect = null;
    }
  };
  if (!option.lazy) {
    effectFn();
  }
  effectFn.scheduler = option.scheduler;
  return effectFn;
}

export function track(target: object, key: string | symbol) {
  if (!activeEffect) {
    return;
  }

  let deps = targetMap.get(target);
  if (!deps) {
    targetMap.set(target, (deps = new Map()));
  }

  let depSet = deps.get(key);
  if (!depSet) {
    deps.set(key, (depSet = new Set()));
  }

  depSet.add(activeEffect);
}

export function trigger(target: object, key: string | symbol) {
  const deps = targetMap.get(target);
  if (deps) {
    const depSet = deps.get(key);
    if (depSet) {
      depSet.forEach((fn) => {
        if (fn.scheduler) {
          fn.scheduler();
        } else {
          fn();
        }
      });
    }
  }
}
